#ifndef TERRAIN_MODEL_H
#define TERRAIN_MODEL_H
#include <memory>
#include <vector>
#include <string>
#include "Point3D.h"
#include "Vector3D.h"
#include "../util/NTuple.h"
#include "TriGrid.h"

namespace geom
{
	const int NUM_HORIZON_SPLITS = 12;
	/** Point class used to store all necessary info for a terrain vertex */
	struct TerrainPoint
        {
            /** Point coords */
            Point3D p;
            /** Point normal*/
            Vector3D n;
            /** Horizon altitudes */
            util::NTuple<float,NUM_HORIZON_SPLITS> horizonValues;
        };

	typedef util::Triplet<unsigned int> IntTri;
	typedef std::vector<IntTri> TrisList;
	typedef std::vector<TerrainPoint> TerrainPointsList;
	/**  @class geom::TerrainModel
	Class to store all necessary information to display a 3D polygonal mesh of a terrain
	 */
    class TerrainModel
    {
        protected:
            /** Triangles in the mesh */
            TrisList m_tris;
            /** Unique points in the mesh */
            TerrainPointsList m_points;

            /** Base color for this terrain */
            util::Triplet<float> m_baseColor;
            mutable geom::TriGrid m_grid;
            mutable bool m_isGridUpToDate;
            void updateGrid() const;
            float m_scale;
            geom::Vector3D m_disp;

        public:
            /** Constructor */
            TerrainModel();
            /** Destructor */
            ~TerrainModel();
            void setDisplacement(geom::Vector3D disp);
            void setScale(float scale);
            float getScale() const{return m_scale;}
            geom::Vector3D getDisplacement() const {return m_disp;}

            /** Sets the base color. The color is in the range 0-1 for each of its components*/
            void setBaseColor(float r, float g, float b);
            /** Gets the current base color */
            const util::Triplet<float>& baseColor() const {return m_baseColor;}

            /** Forms a triangle in the mesh, based on 3 points that were inserted in it in a previous time */
            void addTriangle(const IntTri & indicesTri);

            /** Clears the triangles list */
            void clearTriangles();

            /** Adds another point to the mesh. NOTE: It does not check if it has already been inserted */
            void addPoint(const TerrainPoint &p);
            /** Adds another point to the mesh, but with only the point coordinates defined */
            void addPoint(const geom::Point3D &p);
            /** Gets a point, based on its index */
            const TerrainPoint& getPoint(int pos) const {return m_points.at(pos);}

            /** Changes a point
                          @param pos Index of the point to change.
                          @param p Point to change it to.*/
            void changePoint(int pos, const TerrainPoint &p);
            float getHeightAtCoords(float x, float y) const;

            /** Returns the number of tris in the mesh */
            size_t getNumberOfTris() const {return m_tris.size();}
            /** Returns the number of points in the mesh */
            size_t getNumberOfPoints() const {return m_points.size();}

            /** Read only reference to the mesh points */
            const TerrainPointsList &points() const {return m_points;}
            /** Read-Write reference to the mesh points. */
            TerrainPointsList &points() {return m_points;}
            /** Read only reference to the mesh triangles */
            const TrisList& triangles() const {return m_tris;}
            /** Read-Write reference to the mesh triangles */
            TrisList& triangles(){return m_tris;}

            /** Generates the normals for the whole mesh */
            void generateNormals();
    };
}

#endif
